// File: jmcomic-core/src/main/java/dev/jukomu/jmcomic/util/JmcomicText.java
package dev.jukomu.jmcomic.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import dev.jukomu.common.util.Asserts;
import dev.jukomu.common.util.file.FileUtils;
import dev.jukomu.common.util.system.SystemUtils;
import dev.jukomu.jmcomic.config.JmModuleConfig;
import dev.jukomu.jmcomic.entity.JmAlbumDetail;
import dev.jukomu.jmcomic.entity.JmBaseEntity;
import dev.jukomu.jmcomic.entity.JmPhotoDetail;
import dev.jukomu.jmcomic.entity.Triple;
import dev.jukomu.jmcomic.exception.ExceptionUtils;
import dev.jukomu.jmcomic.exception.JmcomicException;
import dev.jukomu.jmcomic.exception.RegularNotMatchException;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static dev.jukomu.common.util.json.JsonUtils.parseStringAsList;

/**
 * 对应Python的JmcomicText，提供禁漫相关的文本处理工具。
 * 包括URL解析、HTML解析、字符串格式化等。
 */
public class JmcomicText {

    // 正则表达式模式 (对应Python的pattern_jm_domain等)
    public static final Pattern PATTERN_JM_DOMAIN = Pattern.compile("https://([\\w.-]+)");
    public static final List<Map.Entry<Pattern, Integer>> PATTERN_JM_PA_ID = Collections.unmodifiableList(Arrays.asList(
            new AbstractMap.SimpleEntry<>(Pattern.compile("(photos?|albums?)/(\\d+)"), 2),
            new AbstractMap.SimpleEntry<>(Pattern.compile("id=(\\d+)"), 1)
    ));
    public static final Pattern PATTERN_HTML_JM_PUB_DOMAIN = Pattern.compile("[\\w-]+\\.\\w+/?\\w+");

    public static final Pattern PATTERN_HTML_PHOTO_PHOTO_ID = Pattern.compile("<meta property=\"og:url\" content=\".*?/photo/(\\d+)/?.*?\">");
    public static final Pattern PATTERN_HTML_PHOTO_SCRAMBLE_ID = Pattern.compile("var scramble_id = (\\d+);");
    public static final Pattern PATTERN_HTML_PHOTO_NAME = Pattern.compile("<title>([\\s\\S]*?)\\|.*</title>");
    public static final Pattern PATTERN_HTML_PHOTO_DATA_ORIGINAL_DOMAIN = Pattern.compile("src=\"https://(.*?)/media/albums/blank");
    public static final Pattern PATTERN_HTML_PHOTO_DATA_ORIGINAL_0 = Pattern.compile("data-original=\"(.*?)\"[^>]*?id=\"album_photo[^>]*?data-page=\"0\"");
    public static final Pattern PATTERN_HTML_PHOTO_TAGS = Pattern.compile("<meta name=\"keywords\"[\\s\\S]*?content=\"(.*?)\"");
    public static final Pattern PATTERN_HTML_PHOTO_SERIES_ID = Pattern.compile("var series_id = (\\d+);");
    public static final Pattern PATTERN_HTML_PHOTO_SORT = Pattern.compile("var sort = (\\d+);");
    public static final Pattern PATTERN_HTML_PHOTO_PAGE_ARR = Pattern.compile("var page_arr = (.*?);");

    public static final Pattern PATTERN_HTML_B64_DECODE_CONTENT = Pattern.compile("const html = base64DecodeUtf8\\(\"(.*?)\"\\)");
    public static final Pattern PATTERN_HTML_ALBUM_ALBUM_ID = Pattern.compile("<span class=\"number\">.*?：JM(\\d+)</span>");
    public static final Pattern PATTERN_HTML_ALBUM_SCRAMBLE_ID = Pattern.compile("var scramble_id = (\\d+);");
    public static final Pattern PATTERN_HTML_ALBUM_NAME = Pattern.compile("id=\"book-name\"[^>]*?>([\\s\\S]*?)<");
    public static final Pattern PATTERN_HTML_ALBUM_EPISODE_LIST = Pattern.compile("data-album=\"(\\d+)\"[^>]*>[\\s\\S]*?第(\\d+)[话話]([\\s\\S]*?)<[\\s\\S]*?>");
    public static final Pattern PATTERN_HTML_ALBUM_PAGE_COUNT = Pattern.compile("<span class=\"pagecount\">.*?:(\\d+)</span>");
    public static final Pattern PATTERN_HTML_ALBUM_PUB_DATE = Pattern.compile(">上架日期 : (.*?)</span>");
    public static final Pattern PATTERN_HTML_ALBUM_UPDATE_DATE = Pattern.compile(">更新日期 : (.*?)</span>");
    public static final Pattern PATTERN_HTML_TAG_A = Pattern.compile("<a[^>]*?>\\s*(\\S*)\\s*</a>");
    // 作品
    public static final List<Pattern> PATTERN_HTML_ALBUM_WORKS = Collections.unmodifiableList(Arrays.asList(
            Pattern.compile("<span itemprop=\"author\" data-type=\"works\">([\\s\\S]*?)</span>"),
            PATTERN_HTML_TAG_A
    ));
    // 登場人物
    public static final List<Pattern> PATTERN_HTML_ALBUM_ACTORS = Collections.unmodifiableList(Arrays.asList(
            Pattern.compile("<span itemprop=\"author\" data-type=\"actor\">([\\s\\S]*?)</span>"),
            PATTERN_HTML_TAG_A
    ));
    // 标签
    public static final List<Pattern> PATTERN_HTML_ALBUM_TAGS = Collections.unmodifiableList(Arrays.asList(
            Pattern.compile("<span itemprop=\"genre\" data-type=\"tags\">([\\s\\S]*?)</span>"),
            PATTERN_HTML_TAG_A
    ));
    // 作者
    public static final List<Pattern> PATTERN_HTML_ALBUM_AUTHORS = Collections.unmodifiableList(Arrays.asList(
            Pattern.compile("<span itemprop=\"author\" data-type=\"author\">([\\s\\S]*?)</span>"),
            PATTERN_HTML_TAG_A
    ));
    // 點擊喜歡
    public static final Pattern PATTERN_HTML_ALBUM_LIKES = Pattern.compile("<span id=\"albim_likes_\\d+\">(.*?)</span>");
    // 觀看
    public static final Pattern PATTERN_HTML_ALBUM_VIEWS = Pattern.compile("<span>(.*?)</span>\\n *<span>(次觀看|观看次数|次观看次数)</span>");
    // 評論(div)
    public static final Map.Entry<Pattern, Integer> PATTERN_HTML_ALBUM_COMMENT_COUNT = new AbstractMap.SimpleEntry<>(Pattern.compile("<div class=\"badge\"[^>]*?id=\"total_video_comments\">(\\d+)</div>"), 0);

    // 提取接口返回值信息
    public static final Pattern PATTERN_AJAX_FAVORITE_MSG = Pattern.compile("</button>(.*?)</div>");

    // DSL解析器，对应Python的dsl_replacer
    private static final DslReplacer DSL_REPLACER = new DslReplacer();

    static {
        // 注册DSL规则：${VAR_NAME} -> System.getenv("VAR_NAME")
        DSL_REPLACER.addDslAndReplacer("\\$\\{(.*?)\\}", JmcomicText::matchOsEnv);
        // 新增DSL规则：#{PROP_NAME} -> System.getProperty("PROP_NAME")
        DSL_REPLACER.addDslAndReplacer("#\\{(.*?)\\}", JmcomicText::matchJavaProperty);
        // 注册DSL: ${KEY} -> System.getenv(KEY)
        DSL_REPLACER.addDslAndReplacer("\\$\\{(.*?)\\}", match -> {
            String name = match.group(1);
            String value = System.getenv(name);
            Asserts.requireTrue(value != null, "未配置环境变量: " + name);
            return value;
        });
    }

    /**
     * 从系统环境变量中匹配值。
     *
     * @param matcher 正则表达式匹配器，group(1)应为环境变量名
     * @return 环境变量的值
     * @throws AssertionError 如果环境变量未配置
     */
    private static String matchOsEnv(Matcher matcher) {
        String name = matcher.group(1);
        String value = System.getenv(name);
        Asserts.requireTrue(value != null, "未配置环境变量: " + name);
        return value;
    }

    /**
     * 从Java系统属性中匹配值。
     *
     * @param matcher 正则表达式匹配器，group(1)应为属性名
     * @return 系统属性的值
     * @throws AssertionError 如果系统属性未配置
     */
    private static String matchJavaProperty(Matcher matcher) {
        String name = matcher.group(1);
        String value = System.getProperty(name);
        Asserts.requireTrue(value != null, "未配置Java系统属性: " + name);
        return value;
    }

    /**
     * 将文本解析为禁漫域名。
     *
     * @param text 文本，可能是完整URL或仅域名。
     * @return 禁漫域名。
     */
    public static String parseToJmDomain(String text) {
        if (text == null || text.isEmpty()) {
            return "";
        }
        if (text.startsWith(JmModuleConfig.PROT)) {
            Matcher matcher = PATTERN_JM_DOMAIN.matcher(text);
            if (matcher.find()) {
                return matcher.group(1);
            }
        }
        return text;
    }

    /**
     * 将文本解析为禁漫ID（本子ID或章节ID）。
     *
     * @param text 禁漫ID的字符串表示，可以是纯数字、JM开头、或包含在URL中。
     * @return 禁漫ID字符串。
     * @throws JmcomicException 如果无法解析。
     */
    public static String parseToJmId(Object text) {
        if (text == null) {
            throw new JmcomicException("无法解析jm车号, 输入为null", Collections.emptyMap());
        }

        if (text instanceof Integer) {
            return String.valueOf(text);
        }

        Asserts.requireTrue(text instanceof String, "无法解析jm车号, 参数类型为: " + text.getClass().getName());
        String idText = (String) text;

        // 纯数字ID
        if (idText.matches("\\d+")) {
            return idText;
        }

        // JM123456 形式
        Asserts.requireTrue(idText.length() >= 2, "无法解析jm车号, 文本太短: " + idText);
        char c0 = idText.charAt(0);
        char c1 = idText.charAt(1);
        if (('J' == c0 || 'j' == c0) && ('M' == c1 || 'm' == c1)) {
            return idText.substring(2);
        }

        // 从URL中提取ID (e.g., https://xxx/photo/412038, https://xxx/album/?id=412038)
        for (Map.Entry<Pattern, Integer> entry : PATTERN_JM_PA_ID) {
            Matcher matcher = entry.getKey().matcher(idText);
            if (matcher.find()) {
                return matcher.group(entry.getValue());
            }
        }

        throw new JmcomicException("无法解析jm车号, 文本为: " + idText, Collections.emptyMap());
    }

    /**
     * 从HTML中分析禁漫的公共域名。
     *
     * @param html          HTML内容。
     * @param domainKeyword 域名中应包含的关键词，例如 "jm", "comic"。
     * @return 匹配到的域名列表。
     */
    public static List<String> analyseJmPubHtml(String html, String... domainKeyword) {
        List<String> domainList = new ArrayList<>();
        Matcher matcher = PATTERN_HTML_JM_PUB_DOMAIN.matcher(html);
        while (matcher.find()) {
            String domain = matcher.group(0);
            boolean containsKeyword = Arrays.stream(domainKeyword).anyMatch(domain::contains);
            if (domainKeyword.length == 0 || containsKeyword) {
                domainList.add(domain);
            }
        }
        // Python default domainKeyword is ('jm', 'comic')
        if (domainKeyword.length == 0) { // If no keywords provided, use default
            return analyseJmPubHtml(html, "jm", "comic");
        }
        return domainList.stream().distinct().collect(Collectors.toList()); // 去重
    }

    /**
     * 从HTML中分析禁漫的公共域名，使用默认关键词。
     *
     * @param html HTML内容。
     * @return 匹配到的域名列表。
     */
    public static List<String> analyseJmPubHtml(String html) {
        return analyseJmPubHtml(html, "jm", "comic");
    }

    /**
     * 解析禁漫Base64编码的HTML内容。
     *
     * @param respText 响应文本，可能包含Base64编码的HTML。
     * @return 解码后的HTML字符串，如果未编码则返回原始文本。
     */
    public static String parseJmBase64Html(String respText) {
        String htmlB64 = PatternUtils.matchOrDefault(respText, PATTERN_HTML_B64_DECODE_CONTENT, null);
        if (htmlB64 == null) {
            return respText;
        }
        byte[] decodedBytes = java.util.Base64.getDecoder().decode(htmlB64);
        return new String(decodedBytes, java.nio.charset.StandardCharsets.UTF_8);
    }

    /**
     * 解析禁漫本子详情页HTML。
     *
     * @param html HTML内容
     * @return JmAlbumDetail实例
     * @throws RegularNotMatchException 如果关键字段未匹配
     */
    public static dev.jukomu.jmcomic.entity.JmAlbumDetail analyseJmAlbumHtml(String html) {
        String base64DecodedHtml = parseJmBase64Html(html);
        return (JmAlbumDetail) reflectNewInstance(
                base64DecodedHtml,
                "PATTERN_HTML_ALBUM_",
                JmAlbumDetail.class
        );
    }

    /**
     * 解析禁漫章节详情页HTML。
     *
     * @param html HTML内容
     * @return JmPhotoDetail实例
     * @throws RegularNotMatchException 如果关键字段未匹配
     */
    public static JmPhotoDetail analyseJmPhotoHtml(String html) {
        return (JmPhotoDetail) reflectNewInstance(
                html,
                "PATTERN_HTML_PHOTO_",
                JmPhotoDetail.class
        );
    }

    /**
     * 对应Python的reflect_new_instance，通过反射和正则表达式解析HTML并创建实体实例。
     *
     * @param html           HTML内容
     * @param clsFieldPrefix JmcomicText类中Pattern字段的前缀
     * @param clazz          目标实体类
     * @return 实体类实例
     * @throws RegularNotMatchException 如果关键字段未匹配
     */
    private static JmBaseEntity reflectNewInstance(String html, String clsFieldPrefix, Class<?> clazz) {
        Map<String, Object> fieldDict = new HashMap<>();

        // 遍历JmcomicText中的所有Pattern字段
        for (java.lang.reflect.Field field : JmcomicText.class.getFields()) {
            if (field.getName().startsWith(clsFieldPrefix)) {
                try {
                    Object patternObj = field.get(null);
                    String fieldName = field.getName().substring(clsFieldPrefix.length()).toLowerCase();

                    Object fieldValue;
                    if (patternObj instanceof List) {
                        // 如果是Pattern列表，逐级缩小范围并匹配
                        List<Pattern> patterns = (List<Pattern>) patternObj;
                        if (patterns.isEmpty()) continue;

                        String subHtml = html;
                        for (int i = 0; i < patterns.size() - 1; i++) {
                            Matcher matcher = patterns.get(i).matcher(subHtml);
                            if (matcher.find()) {
                                subHtml = matcher.group(0);
                            } else {
                                subHtml = null;
                                break;
                            }
                        }

                        List<String> results = new ArrayList<>();
                        if (subHtml != null) {
                            Matcher finalMatcher = patterns.get(patterns.size() - 1).matcher(subHtml);
                            while (finalMatcher.find()) {
                                results.add(finalMatcher.group(1).trim());
                            }
                        }
                        fieldValue = results;
                    } else if (patternObj instanceof Pattern) {
                        Pattern pattern = (Pattern) patternObj;
                        if (fieldName.endsWith("_list")) {
                            List<String> results = new ArrayList<>();
                            Matcher matcher = pattern.matcher(html);
                            while (matcher.find()) {
                                results.add(matcher.group(1).trim());
                            }
                            fieldValue = results;
                        } else {
                            Matcher matcher = pattern.matcher(html);
                            if (matcher.find()) {
                                fieldValue = matcher.group(1).trim();
                            } else {
                                // 如果关键字段未匹配，抛出异常
                                ExceptionUtils.raisesRegex(
                                        "HTML未匹配上关键字段: " + fieldName,
                                        html.length() > 500 ? html.substring(0, 500) + "..." : html,
                                        pattern
                                );
                                fieldValue = null; // Unreachable
                            }
                        }
                    } else {
                        continue;
                    }

                    // 将字段名转换为驼峰命名法以匹配实体类字段
                    String camelCaseFieldName = toCamelCase(fieldName);
                    fieldDict.put(camelCaseFieldName, fieldValue);
                } catch (IllegalAccessException e) {
                    // Ignore inaccessible fields
                }
            }
        }

        // 修正字段名和类型
        if (clazz == dev.jukomu.jmcomic.entity.JmAlbumDetail.class) {
            renameKey(fieldDict, "album_id", "albumId");
            renameKey(fieldDict, "scramble_id", "scrambleId");
            renameKey(fieldDict, "page_count", "pageCount");
            renameKey(fieldDict, "pub_date", "pubDate");
            renameKey(fieldDict, "update_date", "updateDate");
            renameKey(fieldDict, "comment_count", "commentCount");
            // episode_list 需要特殊处理
            List<Triple<String, String, String>> episodeList = new ArrayList<>();
            Matcher episodeMatcher = PATTERN_HTML_ALBUM_EPISODE_LIST.matcher(html);
            while (episodeMatcher.find()) {
                episodeList.add(Triple.of(episodeMatcher.group(1), episodeMatcher.group(2), episodeMatcher.group(3).trim()));
            }
            fieldDict.put("episodeList", episodeList);
        } else if (clazz == dev.jukomu.jmcomic.entity.JmPhotoDetail.class) {
            renameKey(fieldDict, "photo_id", "photoId");
            renameKey(fieldDict, "scramble_id", "scrambleId");
            renameKey(fieldDict, "series_id", "seriesId");
            // page_arr 是JSON字符串，需要解析为List
            String pageArrJson = (String) fieldDict.get("page_arr");
            if (pageArrJson != null) {
                try {
                    fieldDict.put("pageArr", dev.jukomu.common.util.json.JsonUtils.parseString(pageArrJson, List.class));
                } catch (IOException e) {
                    JmModuleConfig.jmLog("util.parse_html.error", "Failed to parse page_arr JSON: " + pageArrJson);
                    fieldDict.put("pageArr", Collections.emptyList());
                }
            }
        }

        // 使用反射创建实例并填充
        try {
            JmBaseEntity instance = (JmBaseEntity) clazz.getDeclaredConstructor().newInstance();
            for (Map.Entry<String, Object> entry : fieldDict.entrySet()) {
                try {
                    java.lang.reflect.Field field = clazz.getDeclaredField(entry.getKey());
                    field.setAccessible(true);
                    // 类型转换
                    Object value = convertType(entry.getValue(), field.getType());
                    field.set(instance, value);
                } catch (NoSuchFieldException e) {
                    // Ignore fields that don't exist in the entity class
                }
            }
            return instance;
        } catch (Exception e) {
            throw new RuntimeException("Failed to create and populate entity instance: " + clazz.getName(), e);
        }
    }

    private static String toCamelCase(String snakeCase) {
        StringBuilder sb = new StringBuilder();
        boolean nextIsUpper = false;
        for (char c : snakeCase.toCharArray()) {
            if (c == '_') {
                nextIsUpper = true;
            } else {
                sb.append(nextIsUpper ? Character.toUpperCase(c) : c);
                nextIsUpper = false;
            }
        }
        return sb.toString();
    }

    private static void renameKey(Map<String, Object> map, String oldKey, String newKey) {
        if (map.containsKey(oldKey)) {
            map.put(newKey, map.remove(oldKey));
        }
    }

    private static Object convertType(Object value, Class<?> targetType) {
        if (value == null) {
            return null;
        }
        if (targetType.isInstance(value)) {
            return value;
        }
        if (targetType == int.class || targetType == Integer.class) {
            return Integer.parseInt(value.toString());
        }
        if (targetType == long.class || targetType == Long.class) {
            return Long.parseLong(value.toString());
        }
        if (targetType == List.class || targetType == ArrayList.class) {
            try {
                return parseStringAsList(String.valueOf(value));
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
        return value;
    }

    /**
     * 格式化URL，添加协议和域名。
     *
     * @param path   路径，例如 "/album/123/"。
     * @param domain 域名，例如 "18comic.vip"。
     * @return 完整URL。
     */
    public static String formatUrl(String path, String domain) {
        Asserts.requireTrue(domain != null && !domain.isEmpty(), "域名为空");
        if (domain.startsWith(JmModuleConfig.PROT)) {
            return domain + path;
        }
        return JmModuleConfig.PROT + domain + path;
    }

    /**
     * 格式化本子URL。
     *
     * @param albumId 本子ID。
     * @param domain  域名，默认为 "18comic.vip"。
     * @return 本子URL。
     */
    public static String formatAlbumUrl(String albumId, String domain) {
        return formatUrl("/album/" + albumId + "/", domain);
    }

    /**
     * 格式化本子URL，使用默认域名。
     *
     * @param albumId 本子ID。
     * @return 本子URL。
     */
    public static String formatAlbumUrl(String albumId) {
        return formatAlbumUrl(albumId, "18comic.vip");
    }

    /**
     * 将DSL文本解析为绝对路径。
     *
     * @param dslText 包含DSL的文本。
     * @return 解析后的绝对路径。
     */
    public static String parseToAbspath(String dslText) {
        if (dslText == null) {
            return null;
        }
        return FileUtils.fixFilepath(new File(parseDslText(dslText)).getAbsolutePath());
    }

    /**
     * 解析包含DSL的文本。
     *
     * @param dslText 包含DSL的文本。
     * @return 解析后的文本。
     */
    public static String parseDslText(String dslText) {
        if (dslText == null) {
            return null;
        }
        return DSL_REPLACER.parseDslText(dslText);
    }

    /**
     * 括号映射，用于分词。
     */
    private static final Map<Character, Character> BRACKET_MAP = Collections.unmodifiableMap(new HashMap<Character, Character>() {{
        put('(', ')');
        put('[', ']');
        put('【', '】');
        put('（', '）');
    }});

    /**
     * 从本子标题中解析原始名称。
     *
     * @param name 本子标题。
     * @return 原始名称，如果未找到则返回null。
     */
    public static String parseOrigAlbumName(String name) {
        List<String> wordList = tokenize(name);
        for (String word : wordList) {
            if (word.isEmpty()) continue;
            if (BRACKET_MAP.containsKey(word.charAt(0))) {
                continue;
            }
            return word;
        }
        return null;
    }

    /**
     * 对标题进行分词，根据括号分隔。
     * 例如："绕道#2 [暴碧漢化組] [えーすけ（123）] よりみち#2 (COMIC 快樂天 2024年1月號) [中國翻譯] [DL版]"
     * 返回：["绕道#2", "[暴碧漢化組]", "[えーすけ（123）]", "よりみち#2", "(COMIC 快樂天 2024年1月號)", "[中國翻譯]", "[DL版]"]
     *
     * @param title 标题字符串。
     * @return 分词后的字符串列表。
     */
    public static List<String> tokenize(String title) {
        if (title == null || title.isEmpty()) {
            return Collections.emptyList();
        }
        String trimmedTitle = title.trim();
        List<String> result = new ArrayList<>();
        StringBuilder charList = new StringBuilder();
        int i = 0;
        int length = trimmedTitle.length();

        while (i < length) {
            char c = trimmedTitle.charAt(i);

            if (BRACKET_MAP.containsKey(c)) {
                // 上一个单词结束
                addToken(result, charList);
                // 定位右括号
                int j = findRightPair(trimmedTitle, c, i);
                if (j == -1) {
                    // 括号未闭合，作为普通字符处理
                    charList.append(c);
                    i++;
                    continue;
                }
                // 整个括号内的单词结束
                result.add(trimmedTitle.substring(i, j));
                // 移动指针
                i = j;
            } else {
                charList.append(c);
                i++;
            }
        }
        addToken(result, charList);
        return result;
    }

    // 辅助方法：添加分词
    private static void addToken(List<String> result, StringBuilder charList) {
        String word = charList.toString().trim();
        if (!word.isEmpty()) {
            result.add(word);
        }
        charList.setLength(0); // 清空StringBuilder
    }

    // 辅助方法：查找匹配的右括号
    private static int findRightPair(String text, char leftBracket, int startIndex) {
        List<Character> stack = new ArrayList<>();
        stack.add(leftBracket);
        int j = startIndex + 1;
        int length = text.length();

        while (j < length && !stack.isEmpty()) {
            char c = text.charAt(j);
            if (BRACKET_MAP.containsKey(c)) { // 遇到左括号，入栈
                stack.add(c);
            } else if (BRACKET_MAP.containsValue(c)) { // 遇到右括号
                // 检查是否与栈顶的左括号匹配
                if (!stack.isEmpty() && BRACKET_MAP.get(stack.get(stack.size() - 1)) == c) {
                    stack.remove(stack.size() - 1); // 匹配成功，出栈
                } else {
                    // 右括号不匹配栈顶，或者栈已空，视为非法，跳过
                    // 这里我们假设输入是基本合法的，或者我们只匹配最外层的括号
                    // 如果Python代码有更复杂的匹配逻辑，需要调整
                }
            }
            j++;
        }

        if (stack.isEmpty()) {
            return j; // 找到匹配的右括号，返回下一个字符的索引
        } else {
            return -1; // 括号未闭合
        }
    }

    /**
     * 将繁体中文转换为简体中文。
     * 依赖于zhconv库，如果未引入则不进行转换。
     *
     * @param s 原始字符串。
     * @return 转换后的字符串。
     */
    public static String toZhCn(String s) {
        if (s == null || s.isEmpty()) {
            return s;
        }
        try {
            // 使用ZhConverter进行转换
//            return ZhConverter.convert(s, "zh-cn");
            return s;
        } catch (NoClassDefFoundError e) {
            JmModuleConfig.jmLog("warning", "缺少zhconv库，无法进行简繁转换。请添加相关依赖。");
            return s; // 如果库不存在，返回原始字符串
        } catch (Exception e) {
            JmModuleConfig.jmLog("error", "简繁转换失败: " + e.getMessage());
            SystemUtils.printStackTrace();
            return s;
        }
    }

    /**
     * 尝试创建目录，如果目录名过长则进行截断并重试。
     *
     * @param saveDir 要创建的目录路径。
     * @return 实际创建的目录路径。
     * @throws IOException 如果创建目录失败且不是名称过长问题。
     */
    public static String tryMkdir(String saveDir) throws IOException {
        try {
            FileUtils.mkdirIfNotExists(saveDir);
            return saveDir;
        } catch (IOException e) {
            // Errorno 36 (Name too long) is OS specific, not directly in Java IOException
            // In Java, long path errors usually result in AccessDeniedException or similar,
            // or simply the IOException message itself indicates the issue.
            // We'll check the message for common indicators of path too long.
            if (e.getMessage() != null && e.getMessage().toLowerCase().contains("file name too long")) {
                int limit = JmModuleConfig.VAR_FILE_NAME_LENGTH_LIMIT;
                JmModuleConfig.jmLog("error", "目录名过长，无法创建目录，强制缩短到" + limit + "个字符并重试");
                String shortenedDir = saveDir.substring(0, Math.min(saveDir.length(), limit));
                return tryMkdir(shortenedDir); // 递归重试
            }
            throw e; // 重新抛出其他IO异常
        }
    }

    // 私有构造函数，防止实例化
    private JmcomicText() {
    }

    /**
     * 对应Python的DSLReplacer。
     * 用于处理文本中的DSL（领域特定语言），例如环境变量替换。
     */
    private static class DslReplacer {
        private final Map<Pattern, Function<Matcher, String>> dslMap;

        public DslReplacer() {
            this.dslMap = new HashMap<>();
        }

        /**
         * 解析包含DSL的文本。
         *
         * @param text 文本。
         * @return 解析后的文本。
         */
        public String parseDslText(String text) {
            if (text == null || text.isEmpty()) {
                return text;
            }
            String result = text;
            for (Map.Entry<Pattern, Function<Matcher, String>> entry : dslMap.entrySet()) {
                Pattern pattern = entry.getKey();
                Function<Matcher, String> replacer = entry.getValue();
                Matcher matcher = pattern.matcher(result);
                StringBuffer sb = new StringBuffer();
                while (matcher.find()) {
                    matcher.appendReplacement(sb, Matcher.quoteReplacement(replacer.apply(matcher)));
                }
                matcher.appendTail(sb);
                result = sb.toString();
            }
            return result;
        }

        /**
         * 添加DSL规则和替换函数。
         *
         * @param dsl      正则表达式字符串（作为DSL模式）。
         * @param replacer 替换函数，接收Matcher并返回替换字符串。
         */
        public void addDslAndReplacer(String dsl, Function<Matcher, String> replacer) {
            dslMap.put(Pattern.compile(dsl), replacer);
        }
    }
}